# BSD 2-Clause License
#
# Apprise - Push Notification Library.
# Copyright (c) 2025, Chris Caron <lead2gold@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from datetime import datetime
from json import dumps, loads

# Disable logging for a cleaner testing output
import logging
import os
from unittest import mock

from helpers import AppriseURLTester
import pytest
import requests

from apprise import Apprise, AppriseAttachment, NotifyType
from apprise.plugins.office365 import NotifyOffice365

logging.disable(logging.CRITICAL)

# Attachment Directory
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), "var")

# Our Testing URLs
apprise_url_tests = (
    ##################################
    # NotifyOffice365
    ##################################
    (
        "o365://",
        {
            # Missing tenant, client_id, secret, and targets!
            "instance": TypeError,
        },
    ),
    (
        "o365://:@/",
        {
            # invalid url
            "instance": TypeError,
        },
    ),
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/{targets}".format(
            # invalid tenant
            tenant=",",
            cid="ab-cd-ef-gh",
            aid="user@example.com",
            secret="abcd/123/3343/@jack/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            # Expected failure
            "instance": TypeError,
        },
    ),
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/{targets}".format(
            tenant="tenant",
            # invalid client id
            cid="ab.",
            aid="user2@example.com",
            secret="abcd/123/3343/@jack/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            # Expected failure
            "instance": TypeError,
        },
    ),
    (
        "o365://{tenant}/{cid}/{secret}/{targets}".format(
            # email not required if mode is set to self
            tenant="tenant",
            cid="ab-cd-ef-gh",
            secret="abcd/123/3343/@jack/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # Test what happens if a batch send fails to return a messageCount
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                "mail": "user@example.ca",
            },
        },
    ),
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/{targets}".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            aid="user@example.edu",
            secret="abcd/123/3343/@jack/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # Test what happens if a batch send fails to return a messageCount
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                # For 'From:' Lookup
                "mail": "user@example.ca",
            },
            # Our expected url(privacy=True) startswith() response:
            "privacy_url": (
                "azure://user@example.edu/t...t/a...h/****/email1@test.ca/"
            ),
        },
    ),
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/{targets}".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            # Source can also be Object ID
            aid="hg-fe-dc-ba",
            secret="abcd/123/3343/@jack/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # Test what happens if a batch send fails to return a messageCount
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                "mail": "user@example.ca",
                "displayName": "John",
            },
            # Our expected url(privacy=True) startswith() response:
            "privacy_url": (
                "azure://hg-fe-dc-ba/t...t/a...h/****/email1@test.ca/"
            ),
        },
    ),
    # ObjectID Specified, but no targets
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            # Source can also be Object ID
            aid="hg-fe-dc-ba",
            secret="abcd/123/3343/@jack/test",
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # Test what happens if a batch send fails to return a messageCount
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                "mail": "user@example.ca",
            },
            # No emails detected
            "notify_response": False,
            # Our expected url(privacy=True) startswith() response:
            "privacy_url": "azure://hg-fe-dc-ba/t...t/a...h/****",
        },
    ),
    # ObjectID Specified, but no targets
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            # Source can also be Object ID
            aid="hg-fe-dc-ba",
            secret="abcd/123/3343/@jack/test",
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # Test what happens if a batch send fails to return a messageCount
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                "userPrincipalName": "user@example.ca",
            },
            # No emails detected
            "notify_response": False,
            # Our expected url(privacy=True) startswith() response:
            "privacy_url": "azure://hg-fe-dc-ba/t...t/a...h/****",
        },
    ),
    # test our arguments
    (
        "o365://_/?oauth_id={cid}&oauth_secret={secret}&tenant={tenant}"
        "&to={targets}&from={aid}".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            aid="user@example.ca",
            secret="abcd/123/3343/@jack/test",
            targets="email1@test.ca",
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # Test what happens if a batch send fails to return a messageCount
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                "mail": "user@example.ca",
            },
            # Our expected url(privacy=True) startswith() response:
            "privacy_url": (
                "azure://user@example.ca/t...t/a...h/****/email1@test.ca/"
            ),
        },
    ),
    # Test invalid JSON (no tenant defaults to email domain)
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/{targets}".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            aid="user@example.com",
            secret="abcd/123/3343/@jack/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # invalid JSON response
            "requests_response_text": "{",
            "notify_response": False,
        },
    ),
    # No Targets specified
    (
        "o365://{aid}/{tenant}/{cid}/{secret}".format(
            tenant="tenant",
            cid="ab-cd-ef-gh",
            aid="user@example.com",
            secret="abcd/123/3343/@jack/test",
        ),
        {
            # We're valid and good to go
            "instance": NotifyOffice365,
            # There were no targets to notify; so we use our own email
            "requests_response_text": {
                "expires_in": 2000,
                "access_token": "abcd1234",
                "userPrincipalName": "user@example.ca",
            },
        },
    ),
    (
        "o365://{aid}/{tenant}/{cid}/{secret}/{targets}".format(
            tenant="tenant",
            cid="zz-zz-zz-zz",
            aid="user@example.com",
            secret="abcd/abc/dcba/@john/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            "instance": NotifyOffice365,
            # throw a bizarre code forcing us to fail to look it up
            "response": False,
            "requests_response_code": 999,
        },
    ),
    (
        "o365://{tenant}:{aid}/{cid}/{secret}/{targets}".format(
            tenant="tenant",
            cid="01-12-23-34",
            aid="user@example.com",
            secret="abcd/321/4321/@test/test",
            targets="/".join(["email1@test.ca"]),
        ),
        {
            "instance": NotifyOffice365,
            # Throws a series of i/o exceptions with this flag
            # is set and tests that we gracefully handle them
            "test_requests_exceptions": True,
        },
    ),
)


def test_plugin_office365_urls():
    """NotifyOffice365() Apprise URLs."""

    # Run our general tests
    AppriseURLTester(tests=apprise_url_tests).run_all()


@mock.patch("requests.get")
@mock.patch("requests.post")
def test_plugin_office365_general(mock_get, mock_post):
    """NotifyOffice365() General Testing."""

    # Initialize some generic (but valid) tokens
    email = "user@example.net"
    tenant = "ff-gg-hh-ii-jj"
    client_id = "aa-bb-cc-dd-ee"
    secret = "abcd/1234/abcd@ajd@/test"
    targets = "target@example.com"

    # Prepare Mock return object
    payload = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
        # For 'From:' Lookup
        "mail": "abc@example.ca",
        # For our Draft Email ID:
        "id": "draft-id-no",
    }
    response = mock.Mock()
    response.content = dumps(payload)
    response.status_code = requests.codes.ok
    mock_post.return_value = response
    mock_get.return_value = response

    # Instantiate our object
    obj = Apprise.instantiate(f"o365://{email}/{tenant}/{secret}/{targets}")

    assert isinstance(obj, NotifyOffice365)

    # Test our URL generation
    assert isinstance(obj.url(), str)

    # Test our notification
    assert obj.notify(title="title", body="test") is True

    # Instantiate our object
    obj = Apprise.instantiate(
        "o365://{email}/{tenant}/{client_id}/{secret}/{targets}"
        "?bcc={bcc}&cc={cc}".format(
            tenant=tenant,
            email=email,
            client_id=client_id,
            secret=secret,
            targets=targets,
            # Test the cc and bcc list (use good and bad email)
            cc="Chuck Norris cnorris@yahoo.ca, Sauron@lotr.me, invalid@!",
            bcc="Bruce Willis bwillis@hotmail.com, Frodo@lotr.me invalid@!",
        )
    )

    assert isinstance(obj, NotifyOffice365)

    # Test our URL generation
    assert isinstance(obj.url(), str)

    # Test our notification
    assert obj.notify(title="title", body="test") is True

    with pytest.raises(TypeError):
        # No secret
        NotifyOffice365(
            email=email,
            client_id=client_id,
            tenant=tenant,
            secret=None,
            targets=None,
        )

    # One of the targets are invalid
    obj = NotifyOffice365(
        email=email,
        client_id=client_id,
        tenant=tenant,
        secret=secret,
        targets=("Management abc@gmail.com", "garbage"),
    )
    # Test our notification (this will work and only notify abc@gmail.com)
    assert obj.notify(title="title", body="test") is True

    # all of the targets are invalid
    obj = NotifyOffice365(
        email=email,
        client_id=client_id,
        tenant=tenant,
        secret=secret,
        targets=("invalid", "garbage"),
    )

    # Test our notification (which will fail because of no entries)
    assert obj.notify(title="title", body="test") is False


@mock.patch("requests.get")
@mock.patch("requests.post")
def test_plugin_office365_authentication(mock_get, mock_post):
    """NotifyOffice365() Authentication Testing."""

    # Initialize some generic (but valid) tokens
    tenant = "ff-gg-hh-ii-jj"
    email = "user@example.net"
    client_id = "aa-bb-cc-dd-ee"
    secret = "abcd/1234/abcd@ajd@/test"
    targets = "target@example.com"

    # Prepare Mock return object
    authentication_okay = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
    }
    authentication_failure = {
        "error": "invalid_scope",
        "error_description": "AADSTS70011: Blah... Blah Blah... Blah",
        "error_codes": [70011],
        "timestamp": "2020-01-09 02:02:12Z",
        "trace_id": "255d1aef-8c98-452f-ac51-23d051240864",
        "correlation_id": "fb3d2015-bc17-4bb9-bb85-30c5cf1aaaa7",
    }
    response = mock.Mock()
    response.content = dumps(authentication_okay)
    response.status_code = requests.codes.ok
    mock_post.return_value = response
    mock_get.return_value = response

    # Instantiate our object
    obj = Apprise.instantiate(
        f"azure://{email}/{tenant}/{client_id}/{secret}/{targets}"
    )

    assert isinstance(obj, NotifyOffice365)

    # Authenticate
    assert obj.authenticate() is True

    # We're already authenticated
    assert obj.authenticate() is True

    # Expire our token
    obj.token_expiry = datetime.now()

    # Re-authentiate
    assert obj.authenticate() is True

    # Change our response
    response.status_code = 400

    # We'll fail to send a notification now...
    assert obj.notify(title="title", body="test") is False

    # Expire our token
    obj.token_expiry = datetime.now()

    # Set a failure response
    response.content = dumps(authentication_failure)

    # We will fail to authenticate at this point
    assert obj.authenticate() is False

    # Notifications will also fail in this case
    assert obj.notify(title="title", body="test") is False

    # We will fail to authenticate with invalid data

    invalid_auth_entries = authentication_okay.copy()
    invalid_auth_entries["expires_in"] = "garbage"
    response.content = dumps(invalid_auth_entries)
    response.status_code = requests.codes.ok
    assert obj.authenticate() is False

    invalid_auth_entries["expires_in"] = None
    response.content = dumps(invalid_auth_entries)
    assert obj.authenticate() is False

    invalid_auth_entries["expires_in"] = ""
    response.content = dumps(invalid_auth_entries)
    assert obj.authenticate() is False

    del invalid_auth_entries["expires_in"]
    response.content = dumps(invalid_auth_entries)
    assert obj.authenticate() is False


@mock.patch("requests.put")
@mock.patch("requests.get")
@mock.patch("requests.post")
def test_plugin_office365_queries(mock_post, mock_get, mock_put):
    """NotifyOffice365() General Queries."""

    # Initialize some generic (but valid) tokens
    source = "abc-1234-object-id"
    tenant = "ff-gg-hh-ii-jj"
    client_id = "aa-bb-cc-dd-ee"
    secret = "abcd/1234/abcd@ajd@/test"
    targets = "target@example.ca"

    # Prepare Mock return object
    payload = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
        # For 'From:' Lookup (email)
        "mail": "user@example.edu",
        # For 'From:' Lookup (name)
        "displayName": "John",
        # For our Draft Email ID:
        "id": "draft-id-no",
        # For FIle Uploads
        "uploadUrl": "https://my.url.path/",
    }

    okay_response = mock.Mock()
    okay_response.content = dumps(payload)
    okay_response.status_code = requests.codes.ok
    mock_post.return_value = okay_response
    mock_put.return_value = okay_response

    bad_response = mock.Mock()
    bad_response.content = dumps(payload)
    bad_response.status_code = requests.codes.forbidden

    # Assign our GET a bad response so we fail to look up the user
    mock_get.return_value = bad_response

    # Instantiate our object
    obj = Apprise.instantiate(
        f"azure://{source}/{tenant}/{client_id}{secret}/{targets}"
    )

    assert isinstance(obj, NotifyOffice365)

    # We can still send a notification even if we can't look up the email
    assert (
        obj.notify(body="body", title="title", notify_type=NotifyType.INFO)
        is True
    )

    assert mock_post.call_count == 2
    assert (
        mock_post.call_args_list[0][0][0]
        == f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
    )
    assert (
        mock_post.call_args_list[1][0][0]
        == "https://graph.microsoft.com/v1.0/users/abc-1234-object-id/sendMail"
    )
    payload = loads(mock_post.call_args_list[1][1]["data"])
    assert payload == {
        "message": {
            "subject": "title",
            "body": {
                "contentType": "HTML",
                "content": "body",
            },
            "toRecipients": [
                {"emailAddress": {"address": "target@example.ca"}}
            ],
        },
        "saveToSentItems": "true",
    }
    mock_post.reset_mock()

    # Now test a case where we just couldn't get any email details from the
    # payload returned

    # Prepare Mock return object
    temp_payload = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
        # For our Draft Email ID:
        "id": "draft-id-no",
        # For FIle Uploads
        "uploadUrl": "https://my.url.path/",
    }

    bad_response.content = dumps(temp_payload)
    bad_response.status_code = requests.codes.okay
    mock_get.return_value = bad_response

    obj = Apprise.instantiate(
        f"azure://{source}/{tenant}/{client_id}{secret}/{targets}"
    )

    assert isinstance(obj, NotifyOffice365)

    # We can still send a notification even if we can't look up the email
    assert (
        obj.notify(body="body", title="title", notify_type=NotifyType.INFO)
        is True
    )


@mock.patch("requests.put")
@mock.patch("requests.get")
@mock.patch("requests.post")
def test_plugin_office365_attachments(mock_post, mock_get, mock_put):
    """NotifyOffice365() Attachments."""

    # Initialize some generic (but valid) tokens
    source = "user@example.net"
    tenant = "ff-gg-hh-ii-jj"
    client_id = "aa-bb-cc-dd-ee"
    secret = "abcd/1234/abcd@ajd@/test"
    targets = "target@example.com"

    # Prepare Mock return object
    payload = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
        # For 'From:' Lookup
        "mail": "user@example.edu",
        # For our Draft Email ID:
        "id": "draft-id-no",
        # For FIle Uploads
        "uploadUrl": "https://my.url.path/",
    }
    okay_response = mock.Mock()
    okay_response.content = dumps(payload)
    okay_response.status_code = requests.codes.ok
    mock_post.return_value = okay_response
    mock_get.return_value = okay_response
    mock_put.return_value = okay_response

    # Instantiate our object
    obj = Apprise.instantiate(
        f"azure://{source}/{tenant}/{client_id}{secret}/{targets}"
    )

    assert isinstance(obj, NotifyOffice365)

    # Test Valid Attachment
    path = os.path.join(TEST_VAR_DIR, "apprise-test.gif")
    attach = AppriseAttachment(path)
    assert (
        obj.notify(
            body="body",
            title="title",
            notify_type=NotifyType.INFO,
            attach=attach,
        )
        is True
    )

    assert mock_post.call_count == 2
    assert (
        mock_post.call_args_list[0][0][0]
        == f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
    )
    assert (
        mock_post.call_args_list[0][1]["headers"].get("Content-Type")
        == "application/x-www-form-urlencoded"
    )
    assert (
        mock_post.call_args_list[1][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/sendMail"
    )
    assert (
        mock_post.call_args_list[1][1]["headers"].get("Content-Type")
        == "application/json"
    )
    mock_post.reset_mock()

    # Test Authentication Failure
    obj = Apprise.instantiate(
        "azure://{source}/{tenant}/{client_id}{secret}/{targets}".format(
            client_id=client_id,
            tenant=tenant,
            source="object-id-requiring-lookup",
            secret=secret,
            targets=targets,
        )
    )

    bad_response = mock.Mock()
    bad_response.content = dumps(payload)
    bad_response.status_code = requests.codes.forbidden
    mock_post.return_value = bad_response

    assert isinstance(obj, NotifyOffice365)
    # Authentication will fail
    assert (
        obj.notify(
            body="auth-fail", title="title", notify_type=NotifyType.INFO
        )
        is False
    )
    assert mock_post.call_count == 1
    assert (
        mock_post.call_args_list[0][0][0]
        == "https://login.microsoftonline.com/ff-gg-hh-ii-jj/oauth2/v2.0/token"
    )
    mock_post.reset_mock()

    #
    # Test invalid attachment
    #

    # Instantiate our object
    obj = Apprise.instantiate(
        f"azure://{source}/{tenant}/{client_id}{secret}/{targets}"
    )

    assert isinstance(obj, NotifyOffice365)

    mock_post.return_value = okay_response
    path = os.path.join(TEST_VAR_DIR, "/invalid/path/to/an/invalid/file.jpg")
    assert (
        obj.notify(
            body="body",
            title="title",
            notify_type=NotifyType.INFO,
            attach=path,
        )
        is False
    )
    assert mock_post.call_count == 0
    mock_post.reset_mock()

    with mock.patch("base64.b64encode", side_effect=OSError()):
        # We can't send the message if we fail to parse the data
        assert (
            obj.notify(
                body="body",
                title="title",
                notify_type=NotifyType.INFO,
                attach=attach,
            )
            is False
        )
    assert mock_post.call_count == 0
    mock_post.reset_mock()

    #
    # Test case where we can't authenticate
    #
    obj = Apprise.instantiate(
        f"azure://{source}/{tenant}/{client_id}{secret}/{targets}"
    )

    # Force a smaller attachment size forcing us to create an attachment
    obj.outlook_attachment_inline_max = 50

    assert isinstance(obj, NotifyOffice365)

    path = os.path.join(TEST_VAR_DIR, "apprise-test.gif")
    attach = AppriseAttachment(path)
    mock_post.return_value = bad_response
    assert obj.upload_attachment(attach[0], "id") is False
    assert mock_post.call_count == 1
    assert (
        mock_post.call_args_list[0][0][0]
        == "https://login.microsoftonline.com/ff-gg-hh-ii-jj/oauth2/v2.0/token"
    )

    mock_post.reset_mock()

    mock_post.side_effect = (okay_response, bad_response)
    mock_post.return_value = None
    assert obj.upload_attachment(attach[0], "id") is False
    assert mock_post.call_count == 2
    assert (
        mock_post.call_args_list[0][0][0]
        == "https://login.microsoftonline.com/ff-gg-hh-ii-jj/oauth2/v2.0/token"
    )
    assert (
        mock_post.call_args_list[1][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/"
        + "message/id/attachments/createUploadSession"
    )

    mock_post.reset_mock()
    # Return our status
    mock_post.side_effect = None

    # Prepare Mock return object
    payload_no_upload_url = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
        # For 'From:' Lookup
        "mail": "user@example.edu",
        # For our Draft Email ID:
        "id": "draft-id-no",
    }
    tmp_response = mock.Mock()
    tmp_response.content = dumps(payload_no_upload_url)
    tmp_response.status_code = requests.codes.ok
    mock_post.return_value = tmp_response

    assert obj.upload_attachment(attach[0], "id") is False
    assert mock_post.call_count == 1
    assert (
        mock_post.call_args_list[0][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/"
        + "message/id/attachments/createUploadSession"
    )

    mock_post.reset_mock()
    # Return our status
    mock_post.side_effect = None
    mock_post.return_value = okay_response

    obj = Apprise.instantiate(
        f"azure://{source}/{tenant}/{client_id}{secret}/{targets}"
    )

    # Force a smaller attachment size forcing us to create an attachment
    obj.outlook_attachment_inline_max = 50

    assert isinstance(obj, NotifyOffice365)

    # We now have to prepare sepparate session attachments using draft emails
    assert (
        obj.notify(
            body="body",
            title="title-test",
            notify_type=NotifyType.INFO,
            attach=attach,
        )
        is True
    )

    # Large Attachments
    assert mock_post.call_count == 4
    assert (
        mock_post.call_args_list[0][0][0]
        == "https://login.microsoftonline.com/ff-gg-hh-ii-jj/oauth2/v2.0/token"
    )
    assert (
        mock_post.call_args_list[1][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/messages"
    )
    assert (
        mock_post.call_args_list[2][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/"
        + "message/draft-id-no/attachments/createUploadSession"
    )
    assert (
        mock_post.call_args_list[3][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/sendMail"
    )
    mock_post.reset_mock()

    #
    # Handle another case where can't upload the attachment at all
    #
    path = os.path.join(TEST_VAR_DIR, "/invalid/path/to/an/invalid/file.jpg")
    bad_attach = AppriseAttachment(path)
    assert obj.upload_attachment(bad_attach[0], "id") is False

    mock_post.reset_mock()
    #
    # Handle test case where we can't send the draft email after everything
    # has been prepared
    #
    mock_post.return_value = None
    mock_post.side_effect = (okay_response, okay_response, bad_response)
    assert (
        obj.notify(
            body="body",
            title="title-test",
            notify_type=NotifyType.INFO,
            attach=attach,
        )
        is False
    )

    assert mock_post.call_count == 3
    assert (
        mock_post.call_args_list[0][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/messages"
    )
    assert (
        mock_post.call_args_list[1][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/"
        + "message/draft-id-no/attachments/createUploadSession"
    )
    assert (
        mock_post.call_args_list[2][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/sendMail"
    )
    mock_post.reset_mock()
    mock_post.side_effect = None
    mock_post.return_value = okay_response

    #
    # Handle test case where we can not upload chunks
    #
    mock_put.return_value = bad_response

    # We now have to prepare sepparate session attachments using draft emails
    assert (
        obj.notify(
            body="body",
            title="title-no-chunk",
            notify_type=NotifyType.INFO,
            attach=attach,
        )
        is False
    )

    assert mock_post.call_count == 2
    assert (
        mock_post.call_args_list[0][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/messages"
    )
    assert (
        mock_post.call_args_list[1][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/"
        + "message/draft-id-no/attachments/createUploadSession"
    )

    mock_put.return_value = okay_response
    mock_post.reset_mock()

    # Prepare Mock return object
    payload_missing_id = {
        "token_type": "Bearer",
        "expires_in": 6000,
        "access_token": "abcd1234",
        # For 'From:' Lookup
        "mail": "user@example.edu",
        # For FIle Uploads
        "uploadUrl": "https://my.url.path/",
    }
    temp_response = mock.Mock()
    temp_response.content = dumps(payload_missing_id)
    temp_response.status_code = requests.codes.ok
    mock_post.return_value = temp_response

    # We could not acquire an attachment id, so we'll fail to send our
    # notification
    assert (
        obj.notify(
            body="body",
            title="title-test",
            notify_type=NotifyType.INFO,
            attach=attach,
        )
        is False
    )

    # Large Attachments
    assert mock_post.call_count == 1
    assert (
        mock_post.call_args_list[0][0][0]
        == "https://graph.microsoft.com/v1.0/users/user@example.net/messages"
    )

    mock_post.reset_mock()

    # Reset attachment size
    obj.outlook_attachment_inline_max = 50 * 1024 * 1024
    assert (
        obj.notify(
            body="body",
            title="title",
            notify_type=NotifyType.INFO,
            attach=attach,
        )
        is True
    )

    # already authenticated
    assert mock_post.call_count == 1
    assert (
        mock_post.call_args_list[0][0][0]
        == f"https://graph.microsoft.com/v1.0/users/{source}/sendMail"
    )
    mock_post.reset_mock()
